﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using DynORM.Interfaces;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.Globalization;
using DynORM.TypeMappingSubsystem;

namespace DynORM.ValueObjects
{
    [XmlRoot(ElementName = Field.FieldTag)]
    public class Field : IField, IXmlSerializable
    {
        private Type fieldType; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private string fieldName; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private object fieldValue; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private List<object> attributes = new List<object>();

        public Field(Type fieldType, string fieldName, object fieldValue)
        {
            this.fieldType = fieldType;
            this.fieldName = fieldName;
            this.fieldValue = fieldValue;
        }

        public Field(Type fieldType, string fieldName, object fieldValue, object[] attributes)
        {
            this.fieldType = fieldType;
            this.fieldName = fieldName;
            this.fieldValue = fieldValue;
            this.attributes.AddRange(attributes);
        }

        public Field(IFieldDescriptor fieldDescriptor, object fieldValue)
        {
            this.fieldType = fieldDescriptor.FieldType;
            this.fieldName = fieldDescriptor.FieldName;
            this.fieldValue = fieldValue;
        }


        //for serializer
        private Field()
        { }

        public const string FieldTag = "Field";
        public const string FieldNameAttr = "FieldName";
        public const string FieldTypeAttr = "FieldType";
        public const string FieldAttributesAttr = "Attribute";

        public Type FieldType
        {
            get { return this.fieldType; }
        }

        public string FieldName
        {
            get { return this.fieldName; }
        }

        public object FieldValue
        {
            get { return this.fieldValue; }
        }

        public object[] Attributes
        {
            get { return this.attributes.ToArray(); }
        }

        public CheckIntegrityResult CheckValuesIntegrity(bool usePredefinedConvention)
        {
            CheckIntegrityResult passed = new CheckIntegrityResult(true, String.Empty);

            //just because we construct Fields not only from IFieldDescriptors we have to recheck structure integrity before value integrity
            CheckIntegrityResult checkStructureIntegrityRes = this.GetDescriptor().CheckStructureIntegrity(false);
            if (checkStructureIntegrityRes != passed)
                return checkStructureIntegrityRes;

            if (Types.IsCLRTypeNullable(this.fieldType) == false && this.fieldValue == null)
                return new CheckIntegrityResult(false, "Field value is null and field type doesn't allows nulls.");


            if (this.fieldValue != null)
                if (Types.GetCLRCompatibleTypes(this.fieldValue.GetType()).Any<Type>(t => this.fieldType == t) == false)
                    return new CheckIntegrityResult(false, "Field contains value that is not corresponding to the field type.");

            return passed;
        }

        public CheckIntegrityResult CheckOnlyValuesIntegrity(bool usePredefinedConvention)
        {
            //this method is used in case when several rows from the same table are retrieved. in this case structure (RowDescriptor) is checked only once and values (Rows) check do not include structure tests
            CheckIntegrityResult passed = new CheckIntegrityResult(true, String.Empty);

            if (Types.IsCLRTypeNullable(this.fieldType) == false && this.fieldValue == null)
                return new CheckIntegrityResult(false, "Field value is null and field type doesn't allows nulls.");


            if (this.fieldValue != null)
                if (Types.GetCLRCompatibleTypes(this.fieldValue.GetType()).Any<Type>(t => this.fieldType == t) == false)
                    return new CheckIntegrityResult(false, "Field contains value that is not corresponding to the field type.");

            return passed;
        }

        public IORMObjectDescriptor GetDescriptor()
        {
            return new FieldDescriptor(this.fieldType, this.fieldName);
        }

        #region Value-based equality semantics implementation
        public override bool Equals(object obj)
        {
            IField that = obj as IField;
            if (Object.ReferenceEquals(that, null))
                return false;

            //it's important to use this.FieldValue.Equals(that.FieldValue) instead of this.FieldValue == that.FieldValue
            //overloaded equality semantics used only when we use this.FieldValue.Equals(that.FieldValue)

            if ((FieldDescriptor)this.GetDescriptor() == (FieldDescriptor)that.GetDescriptor() &&
                ((this.FieldValue != null && this.FieldValue.Equals(that.FieldValue)) || (this.FieldValue == null && that.FieldValue == null)))
                return true;
            else
                return false;
        }

        public override int GetHashCode()
        {
            return this.FieldName.GetHashCode();
        }

        public static bool operator ==(Field f1, Field f2)
        {
            if (Object.ReferenceEquals(f1, null))
                return false;
            return f1.Equals(f2);
        }

        public static bool operator !=(Field f1, Field f2)
        {
            if (Object.ReferenceEquals(f1, null))
                return false;
            return !f1.Equals(f2);
        }

        #endregion

        #region IXmlSerializable and auxiliary CreateFromXml member
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, FieldTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        if (string.Compare(reader.Name, FieldNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.fieldName = reader.Value;
                                        else if (string.Compare(reader.Name, FieldTypeAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.fieldType = Type.GetType(reader.Value);
                                        else if (string.Compare(reader.Name, FieldAttributesAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.attributes.Add(Int32.Parse(reader.Value));
                                    }
                                }
                                if (emptyElement)
                                {
                                    this.fieldValue = null;
                                    return;
                                }
                                //else
                                //{ 
                                //    this.fieldValue = reader.ReadContentAs(this.fieldType, null);
                                //}
                            }
                        } break;
                    case XmlNodeType.Text:
                        {
                            Type type;
                            if (Types.IsCLRTypeNullable(this.fieldType) && this.fieldType != typeof(string))
                                type = this.fieldType.GetGenericArguments().First<Type>();
                            else
                                type = this.fieldType;

                            if (type == typeof(Guid))
                                this.fieldValue = Guid.Parse((string)reader.ReadContentAs(typeof(string), null));
                            else if (type == typeof(Boolean))
                                this.fieldValue = Boolean.Parse((string)reader.ReadContentAs(typeof(string), null));
                            else if (type == typeof(DateTime))
                                this.fieldValue = DateTime.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                                //this.fieldValue = reader.ReadContentAsDateTime();
                            else if (type == typeof(Decimal))
                                this.fieldValue = Decimal.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                            else if (type == typeof(Single))
                                this.fieldValue = Single.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                            else if (type == typeof(Double))
                                this.fieldValue = Double.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                            else
                                this.fieldValue = reader.ReadContentAs(type, null);
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, FieldTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return;
                        } break;
                    default: continue;
                }
            } while (reader.Read());
        }

        public static Field CreateFromXml(XmlReader reader)
        {
            string fieldName = String.Empty;
            Type fieldType = typeof(string);
            object fieldValue = null;
            List<object> attributes = new List<object>();

            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, FieldTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        if (string.Compare(reader.Name, FieldNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            fieldName = reader.Value;
                                        else if (string.Compare(reader.Name, FieldTypeAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            fieldType = Type.GetType(reader.Value);
                                        else if (string.Compare(reader.Name, FieldAttributesAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            attributes.Add(Int32.Parse(reader.Value));
                                    }
                                }
                                if (emptyElement)
                                {
                                    fieldValue = null;
                                    return new Field(fieldType,fieldName,fieldValue);
                                }
                                //else
                                //{ 
                                //    this.fieldValue = reader.ReadContentAs(this.fieldType, null);
                                //}
                            }
                        } break;
                    case XmlNodeType.Text:
                        {
                            Type type;
                            if (Types.IsCLRTypeNullable(fieldType) && fieldType != typeof(string))
                                type = fieldType.GetGenericArguments().First<Type>();
                            else
                                type = fieldType;

                            if (type == typeof(Guid))
                                fieldValue = Guid.Parse((string)reader.ReadContentAs(typeof(string), null));
                            else if (type == typeof(DateTime))
                                fieldValue = DateTime.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                                //this.fieldValue = reader.ReadContentAsDateTime();
                            else if (type == typeof(Single))
                                fieldValue = Single.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                            else if (type == typeof(Double))
                                fieldValue = Double.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                            else if (type == typeof(bool))
                                fieldValue = Boolean.Parse((string)reader.ReadContentAs(typeof(string), null));
                            else if (type == typeof(Decimal))
                                fieldValue = Decimal.Parse((string)reader.ReadContentAs(typeof(string), null), CultureInfo.CreateSpecificCulture("en-GB"));
                            else
                                fieldValue = reader.ReadContentAs(type, null);
                            return new Field(fieldType, fieldName, fieldValue);
                        } 
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, FieldTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return new Field(fieldType, fieldName, fieldValue);
                        } break;
                    default: continue;
                }
            } while (reader.Read());
            if (attributes.Count == 0)
                return new Field(fieldType, fieldName, fieldValue);
            else
                return new Field(fieldType, fieldName, fieldValue, attributes.ToArray());
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString(FieldNameAttr, this.FieldName);
            writer.WriteAttributeString(FieldTypeAttr, this.FieldType.FullName);
            if (this.attributes.Count > 0)
                foreach (object attr in this.attributes)
                    writer.WriteAttributeString(FieldAttributesAttr, attr.ToString());

            if (this.FieldValue != null)
            {
                if (this.FieldType == typeof(DateTime))
                    writer.WriteString(((DateTime)this.FieldValue).ToString(CultureInfo.CreateSpecificCulture("en-GB")));
                else if (this.FieldType == typeof(Decimal))
                    writer.WriteString(((Decimal)this.FieldValue).ToString(CultureInfo.CreateSpecificCulture("en-GB")));
                else if (this.FieldType == typeof(Double))
                    writer.WriteString(((Double)this.FieldValue).ToString(CultureInfo.CreateSpecificCulture("en-GB")));
                else if (this.FieldType == typeof(Single))
                    writer.WriteString(((Single)this.FieldValue).ToString(CultureInfo.CreateSpecificCulture("en-GB")));
                else
                    writer.WriteString(this.FieldValue.ToString());
            }
        }
        #endregion    

    }

}
